import 'dart:async';
import 'dart:io';

import 'package:bluetooth_ble/bluetooth_ble.dart';
import 'package:bluetooth_spp/bluetooth_spp.dart';
import 'package:flutter/material.dart';
import 'package:sxw_bluetooth_provider/src/config/config.dart';

import 'core/ble_delegate.dart';
import 'core/connection.dart';
import 'core/spp_delegate.dart';
import 'device/bluetooth_device.dart';
import 'device/platform_enum.dart';
import 'error/bt_error.dart';
import 'utils/nohup_completer.dart';

class SxwBluetoothProvider with ChangeNotifier, ConnectionDelegate {
  Spp get spp => Spp();

  BluetoothBle get ble => BluetoothBle();

  static SxwBluetoothProvider _instance;

  SxwBluetoothProvider._();

  factory SxwBluetoothProvider() {
    _instance ??= SxwBluetoothProvider._();
    return _instance;
  }

  BleDelegate bleDelegate;
  SppDelegate sppDelegate;

  Future<void> waitEnable() async {
    if (!await isEnable()) {
      if (!Platform.isAndroid) {
        throw BtErrors.switchNoOpen;
      }
      final completer = Completer();
      StreamSubscription sub;
      sub = spp.switchStream.listen((enable) {
        if (enable) {
          sub.cancel();
        }
        completer.complete();
      });

      spp.enable();
      return completer.future;
    }
  }

  Future<bool> isEnable() async {
    if (Platform.isAndroid) {
      return spp.isEnabled();
    } else if (Platform.isIOS) {
      return ble.isEnabled();
    }
    throw BtErrors.notSupportPlatform;
  }

  Future<SxwBluetoothDevice> connectBle(BleConfig config) async {
    await waitEnable();
    final completer = NoHupCompleter<SxwBluetoothDevice>();
    bleDelegate ??= BleDelegate(this);
    bleDelegate.connect(config, completer);
    return completer.future;
  }

  Future<SxwBluetoothDevice> connectSpp(SppConfig config) async {
    if (!Platform.isAndroid) {
      throw BtErrors.notSupportPlatform;
    }
    await waitEnable();
    final completer = NoHupCompleter<SxwBluetoothDevice>();
    sppDelegate ??= SppDelegate(this);
    sppDelegate.connect(config, completer);
    return completer.future;
  }

  List<SxwBluetoothDevice> get devices {
    final bleList =
        bleDelegate?.connectedDeviceList?.cast<SxwBluetoothDevice>();
    final sppList =
        sppDelegate?.connectedDeviceList?.cast<SxwBluetoothDevice>();
    return ((bleList ?? []) + (sppList ?? [])).toList();
  }

  List<SxwBluetoothDevice> findDeviceByType(PlatformEnum type) {
    return devices.where((test) => test.platformEnum == type).toList();
  }

  @override
  void onConnectedDeviceChange() {
    notifyListeners();
  }

  @override
  void onNotifyNotFoundCh(BleDevice device) {
    device.disconnect();
  }
}

enum BtType {
  spp,
  ble,
}
